#include <conio.h>
#include <irq.h>
#include <string.h>
#include <schedule.h>
#include <sys/kmalloc.h>
#include <sys/task.h>
#include <sys/thread.h>
#include <sys/unistd.h>
#include <sys/vmm.h>

task_t kern_task;		/* kernel task data struct */
static list_t tasks_list;	/* list of all tasks in system */
static int ntasks;

int task_create(task_t * parent, task_t ** child, int vm_type)
{
    task_t *task = NULL;
    vm_map_t *vmap = NULL;

    assert(parent != NULL);

    sched_lock();
    if (!task_valid(parent))
	return -1;

    /* must be system task */
    if ((parent->flags & TASK_SYSTEM) == 0) {
	task = 0;
	if (copyout(&task, child, sizeof(task))) {
	    sched_unlock();
	    return -1;
	}
    }

    task = (task_t *) kmalloc(sizeof(task_t), GFP_KERNEL);
    if (task == NULL) {
	sched_unlock();
	return -1;
    }

    /* copying task struct of parent to child */
    //memcpy((void *)child, (void *)parent, sizeof(task_t));
    memset(task, 0, sizeof(task_t));
    switch (vm_type) {
    case VM_NEW_MEM:{
	    vmap = vm_create();
	    break;
	};
    case VM_SHARE_MEM:{
	    vm_ref(parent->vmap);
	    vmap = parent->vmap;
	    break;
	};
    case VM_COPY_MEM:{
	    vmap = vm_dup(parent->vmap);
	    break;
	};
    default:{
	    break;
	};
    };

    if (vmap == NULL) {
	kfree(task);
	sched_unlock();

	return -1;
    }

    /* fill out task data */
    task->vmap = vmap;
    task->flags = TASK_DEFAULT;
    task->parent = parent;
    task->suspend_cnt = 0;

    list_init(&task->threads);
    list_init(&task->sems);
    list_init(&task->mutexes);
    list_init(&task->ipc_objs);

    task->nthreads = 0;
    task->nobjects = 0;
    strcpy(task->name, "*noname");
    list_insert(&tasks_list, &task->link);
    *child = task;
    ntasks++;

    if (curtask->flags & TASK_SYSTEM)
	*child = task;
    else {
	/*
	 * no page fault here because we have already
	 * checked it.
	 */
	copyout(&task, child, sizeof(task));
    }
    sched_unlock();

    return 0;
}


task_t *task_self(void)
{
    return (curth->owner);
}

int task_terminate(task_t * task)
{
    list_t *head, *n;
    thread_t *th = NULL;

    sched_lock();
    if (!task_valid(task)) {
	sched_unlock();
	return -1;
    }
    list_remove(&task->link);
    //task->handler = EXC_DFL;

    /*
     * clean up all resources owned by the target task.
     */
    //timer_stop(&task->alarm);
    ipc_cleanup(task);
    //mutex_cleanup(task);
    //cond_cleanup(task);
    sem_cleanup(task);

    /*
     * terminate each thread in the task.
     */
    head = &task->threads;
    for (n = list_first(head); n != head; n = list_next(n)) {
	th = list_entry(n, thread_t, task_link);
	if (th != curth)
	    thread_destroy(th);
    };
    if (task == curtask)
	thread_destroy(curth);

    vm_terminate(task->vmap);
    task->vmap = NULL;
    kfree(task);
    ntasks--;

    sched_unlock();

    return 0;
}

/*
 * suspend a task.
 */
int task_suspend(task_t * task)
{
    list_t *head, *n;
    thread_t *th = NULL;

    sched_lock();
    if (!task_valid(task)) {
	sched_unlock();
	return -1;
    }

    if (++task->suspend_cnt == 1) {
	/*
	 * suspend all threads within the task.
	 */
	head = &task->threads;
	for (n = list_first(head); n != head; n = list_next(n)) {
	    th = list_entry(n, thread_t, task_link);
	    thread_suspend(th);
	};
    }
    sched_unlock();

    return 0;
}

/*
 * resume a task.
 *
 * A thread in the task will begin to run only when both
 * thread suspend count and task suspend count become 0.
 */
int task_resume(task_t * task)
{
    list_t *head, *n;
    thread_t *th;

    assert(task != curtask);

    sched_lock();
    if (!task_valid(task)) {
	sched_unlock();
	return -1;
    }
    if (task->suspend_cnt == 0) {
	sched_unlock();
	return -1;
    }
    if (--task->suspend_cnt == 0) {
	/*
	 * resume all threads in the target task.
	 */
	head = &task->threads;
	for (n = list_first(head); n != head; n = list_next(n)) {
	    th = list_entry(n, thread_t, task_link);
	    thread_resume(th);
	};
    }
    sched_unlock();

    return 0;
}

int task_setname(task_t * task, const char *name)
{
    char str[TASKNAME_MAX];
    int error = 0;

    sched_lock();
    if (!task_valid(task)) {
	sched_unlock();
	return -1;
    }
    if (curtask->flags & TASK_SYSTEM) {
	strncpy(task->name, name, TASKNAME_MAX);
    } else {
	error = copyinstr(name, str, TASKNAME_MAX);
	if (error) {
	    sched_unlock();
	    return (error);
	}
	strncpy(task->name, str, TASKNAME_MAX);
    }
    sched_unlock();

    return 0;
}

int task_valid(task_t *task)
{
    task_t *tmp = NULL;
    list_t *n;

    for (n = list_first(&tasks_list); n != &tasks_list; n = list_next(n)) {
	tmp = list_entry(n, task_t, link);
	if (tmp == task)
	    return 1;
    };

    return 0;
}

void task_init(void)
{
    kprintf("tasking init...");
    list_init(&tasks_list);

    strcpy(kern_task.name, "kernel");
    kern_task.flags = TASK_SYSTEM;
    kern_task.nthreads = 0;

    list_init(&kern_task.threads);
    list_init(&kern_task.sems);
    list_init(&kern_task.mutexes);
    list_init(&kern_task.ipc_objs);

    kern_task.nobjects = 0;
    ntasks++;

    list_insert(&tasks_list, &kern_task.link);
    kprintf("Ok.\n");
}
